import random
import gamesprite
from pygame.sprite import Sprite
from driver import *
from tools import *

class MiniSprite(Sprite):
    """all sprite's super class"""
    def __init__(self, node, datamap, hold = [(0, 0)], layer = 0, visible = True, logical = True):
        super(MiniSprite, self).__init__()
        self.image = None
        self.rect = None    # for draw, interger position
        self.node = node # for refrence map
        self.pos = list(node2pixel(self.node))   # for calc and change value, exact float position
        self.datamap = datamap
        self.driver = Driver()
        self.attr = {}
        self.hold = hold # datamap node offset relative to position node
        self.parent = None
        self.children = []
        self.layer = layer # this sprite's render layer, bigger will cover smaller
        self.visible = visible # render and refmap
        self.logical = logical # update
    
    def link(self, spr): 
        """ if produce a child, link to self """
        spr.parent = self
        self.children.append(spr)
        
    def unlink(self): 
        """ if kill, only no children, and has parent, will unlink """
        if not self.children \
        and self.parent \
        and not self.alive():
            self.parent.children.remove(self)
            self.parent.unlink()
            self.parent = None
        
    def kill(self):
        if self.visible: self.datamap.ref_delete(self)
        super(MiniSprite, self).kill()
        self.unlink() # remove self from sprite tree after kill
        
    def update(self):
        """ update sprite rect """
        self.rect = pygame.Rect((self.pos[0], self.pos[1]), self.rect.size)
        super(MiniSprite, self).update()

##################################################
# sprite visual attribute
##################################################
class StaticSprite(MiniSprite):
    """sprite's image is static"""
    ATTRNAME = 'static'
    def stc_init(self, imagedict, index):
        self.attr[StaticSprite.ATTRNAME] = True
        self.__imagedict = imagedict
        self.__index = index
        self.image = self.__imagedict[self.__index]
        self.rect = self.image.get_rect(topleft = self.pos)
        
    def stc_change(self, index): # can change the image
        self.__index = index
        self.image = self.__imagedict[self.__index]
        
class DynamicSprite(MiniSprite):
    """sprite's image can change"""
    ATTRNAME = 'dynamic'
    def dnc_init(self, framedict, section, rate = 10):
        self.attr[DynamicSprite.ATTRNAME] = True
        self.__section = section
        self.__index = 0
        self.__tick = 0
        self.__rate = rate        
        self.__framedict = framedict
        self.image = self.__framedict[self.__section][self.__index]
        self.rect = self.image.get_rect(topleft = self.pos)
        
    def dnc_animate(self, section = None): 
        """ animate sprite's frame """
        if section == None or self.__section == section: # section not change, section can be 0
            self.__tick += 1
            if self.__tick % self.__rate == 0:
                self.__tick = 0
                self.__index = (self.__index + 1) % len(self.__framedict[self.__section])
        else:
            self.__section = section
            self.__index = 0
            self.__tick = 0
        self.image = self.__framedict[self.__section][self.__index]

class ThroughSprite(MiniSprite):
    """sprite can allow some sprite through self"""
    ATTRNAME = 'through'
    def tru_init(self):
        self.attr[ThroughSprite.ATTRNAME] = True
        self.__sprcls = {} # these sprite class can through me
    
    def tru_register(self, sprcls):
        for cls in sprcls: # sprite's class
            self.__sprcls[cls] = True
    
    def tru_unregister(self, sprcls):
        for cls in sprcls:
            del self.__sprcls[cls]
    
    def tru_passable(self, spr):
        return self.__sprcls.has_key(spr.__class__)

##################################################
# sprite functional attribute
##################################################
class IntelligentSprite(MiniSprite):
    """sprite can update path point from Ai"""
    ATTRNAME = 'intelligent'
    def itg_init(self, ai, level = 0, rate = 53):
        self.attr[IntelligentSprite.ATTRNAME] = True
        self.__ai = ai
        self.__level = level # intelligent level, 4 is best
        self.__rate = rate
        self.__tick = 0
    
    def itg_level(self):
        return self.__level
    
    def itg_think(self):
        if self.__tick % self.__rate == 0:
            basenode = []
            playernode = []
            # convert base and player position to datamap node
            for base in self.datamap.sprgroup.sprites((gamesprite.Base, )):
                basenode.append(base.node)
            for player in self.datamap.sprgroup.sprites((gamesprite.Player, gamesprite.Plane)):
                playernode.append(player.node)
            # update ai        
            if len(playernode) > 0:
                self.__ai.update(self, self.node, basenode, playernode, self.__level)
        self.__tick = (self.__tick + 1) % self.__rate  


class VitalSprite(MiniSprite):
    """sprite have health, 0 means dead"""
    ATTRNAME = 'vital'
    def vtl_init(self, hp, maxhp = 0):
        self.attr[VitalSprite.ATTRNAME] = True
        self.__hp = hp
        self.__maxhp = maxhp

    def vtl_maxhp(self, maxhp):
        self.__maxhp = maxhp

    def vtl_state(self):
        return self.__hp

    def vtl_change(self, power = 0, all = False):
        if power >= 0: # if add health
            self.__hp += power
            if self.__maxhp > 0 \
            and self.__hp > self.__maxhp: 
                self.__hp = self.__maxhp
        else: # if sub
            if all: # if all health judge
                if self.__hp + power <= 0: self.__hp = 0
            else:
                self.__hp += power
                if self.__hp < 0: self.__hp = 0
    
    def vtl_alive(self):
        if self.__hp > 0: return True
        else: return False
        
class DropSprite(MiniSprite):
    """ sprite can drop some thing """
    ATTRNAME = 'drop'
    def drp_init(self, drop):
        self.attr[VitalSprite.ATTRNAME] = True
        self.__drop = drop # item: (odds, cls, *args)
        
    def drp_drop(self):
        drops = []
        odds = random.randint(0, 100)
        for item in self.__drop:
            if odds <= item[0]:
                drops.append(item)
        if drops:
            item = self.__drop[random.randint(0, len(drops) - 1)]
            node = (random.randint(0, self.datamap.XAXIS - 2), 
                    random.randint(0, self.datamap.YAXIS - 2))
            self.driver.send(ProduceSignal(0, item[1], node, self.datamap, *item[2]))
        
##################################################
# sprite signal process attribute
##################################################
class KeyboardSprite(MiniSprite):
    """sprite can process keyboard signal"""
    ATTRNAME = 'keyboard'
    def kbd_init(self):
        self.__keyfunc = {}
    
    def kbd_register(self, keys, func):
        for key in keys:
            self.__keyfunc[key] = func
    
    def kbd_unregister(self, keys):
        for key in keys:
            del self.__keyfunc[key]
    
    def kbd_keysig(self): 
        """ call key-bounding function """
        for sig in self.driver.receive(KeySignal.KIND):
            if self.__keyfunc.has_key(sig.key):
                self.__keyfunc[sig.key](sig.key)
        self.driver.clear(KeySignal.KIND)
    
class CollisionSprite(MiniSprite):
    """sprite can process collision signal"""
    ATTRNAME = 'collision'
    def col_init(self):
        self.__collfunc = {}
    
    def col_register(self, sprcls, func):
        for cls in sprcls: # sprite's class
            self.__collfunc[cls] = func
    
    def col_unregister(self, sprcls):
        for cls in sprcls:
            del self.__collfunc[cls]
            
    def col_collisionsig(self): 
        """ call collision-bounding function """
        for sig in self.driver.receive(CollisionSignal.KIND):
            if self.__collfunc.has_key(sig.spr.__class__): # collided map bound, if None
                self.__collfunc[sig.spr.__class__](sig.spr)
        self.driver.clear(CollisionSignal.KIND)


class MovableSprite(MiniSprite):
    """sprite can move"""
    ATTRNAME = 'movable'
    def mov_init(self, speed):
        self.attr[MovableSprite.ATTRNAME] = True
        self.__speed = speed # speed > 0
    
    def mov_move(self, dirvec): 
        # use vector speed
        vecspeed = (dirvec[0] * self.__speed, dirvec[1] * self.__speed)
        self.pos[0] += vecspeed[0]
        self.pos[1] += vecspeed[1]
        return (abs(vecspeed[0]) + abs(vecspeed[1])) # move distance

    def mov_pathsig(self): 
        """ process path signal """
        if self.driver.check(PathSignal.KIND) \
        and (not self.driver.check(GotoSignal.KIND)): # have path point and current not gotosignal
            path = self.driver.receive(PathSignal.KIND)[0]
            direct = path.direct # will return
            # stop, if will collide
            testnode = (self.node[0] + GotoSignal.DIR2VEC[path.direct][0], 
                        self.node[1] + GotoSignal.DIR2VEC[path.direct][1])
            if self.datamap.ref_change(self, testnode):
                if not path.force: self.driver.pop(PathSignal.KIND) # discard
            else:
                self.driver.pop(PathSignal.KIND) # not collision, remove one path point
                self.driver.send(GotoSignal(GotoSignal.DIR2VEC[path.direct]))
            return direct # return this path direct

    def mov_gotosig(self):
        """ process goto signal """
        if self.driver.check(GotoSignal.KIND):
            goto = self.driver.receive(GotoSignal.KIND)[0]
            goto.distance -= self.mov_move(goto.dirvec)
            if goto.distance <= 0: self.driver.clear(GotoSignal.KIND)
            return True # True if moved

class ProducerSprite(MiniSprite):
    """can produce another sprite"""
    ATTRNAME = 'factory'
    def pdc_init(self):
        self.__ratedict = {}
    
    def pdc_colding(self):
        """ colding all """
        for sprcls in self.__ratedict.keys():
            self.__ratedict[sprcls] -= 1
            if self.__ratedict[sprcls] <= 0: # cold down
                del self.__ratedict[sprcls]
    
    def pdc_check(self, sprcls, rate):
        """ if colding return false, if cold down return true and new begin colding """
        if self.__ratedict.has_key(sprcls): # this spriteclass is colding
            return False
        else: # colded, and begin cold timer
            if rate > 0: self.__ratedict[sprcls] = rate
            return True
    
    def pdc_produce(self, sprcls, *args, **keywords):
        """ generate appoint sprite and init it group """
        spr = sprcls(*args, **keywords) # generator instance
        # add to it's group
        self.datamap.sprgroup.add(spr)
        if spr.logical:
            self.datamap.updates.add(spr)
        if spr.visible: 
            self.datamap.render.add(spr)
            self.datamap.ref_add(spr, spr.node)
        self.link(spr)

    def pdc_producesig(self):
        """ process produce signal """
        self.pdc_colding()
        if self.driver.check(ProduceSignal.KIND):
            for pdc in self.driver.receive(ProduceSignal.KIND):
                if self.pdc_check(pdc.sprcls, pdc.rate): # if colded
                    self.pdc_produce(pdc.sprcls, *pdc.args, **pdc.keywords)
        self.driver.clear(ProduceSignal.KIND)
